/*
    Copyright 2006-2011 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/// \file
/// Definitions of handy functors.

// $Id$

#ifndef __functors__
#define __functors__

#include "random.h"
#include "boost/function.hpp"

namespace mcrx {
  namespace functors {
    typedef boost::function<T_float(int, const particle_set_generic&, int, particle_set_generic&)> T_particle_functor;

    /// \name Functors for age distribution.
    /// @{
    class dummy_tform; 
    class own_tform;
    class constant;
    class stochastic_uniform;
    class stochastic_exponential;
    class range_clip;
    ///@}

    /// \name Functors for metallicity distribution.
    ///@{
    class parent_radial_exponential;
    ///@}

    /// Compounder, used to compound the age & z functor.
    class compound;
  };
};


/** Functor which returns 0 regardless of specified particle.  This is
    used as a dummy for particles that do not have formation time.  */
class mcrx::functors::dummy_tform {
public:
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    return 0;};
};


/** Functor which returns the formation time of the specified particle.  */
class mcrx::functors::own_tform {
public:
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    // we must downcast here since tform is not a member of base class
    return dynamic_cast<const star_particle_set&>(s).tf(num);
  };
};


/** Functor which returns a specified constant regardless of specified
    particle.  */
class mcrx::functors::constant {
private:
  const double c;
public:
  constant (double cc): c (cc) {};
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    return c;
  }
};


/** Functor which returns a random number uniformly distributed in the
    specified range (regardless of particle).  */
class mcrx::functors::stochastic_uniform {
private:
  const double x0, r;
public:
  stochastic_uniform (double xx0, double xx1) : x0(xx0), r (xx1-xx0) {};
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    return r*mcrx::rnd()+x0;
  }
};


/** This functor returns a random number drawn from an exponential
    distribution between [0,a] with an exponential constant t. */
class mcrx::functors::stochastic_exponential {
private:
  const double fage; ///< Fractional age, start age/tau
  const double tau; ///< Exponential decay constant
public:
  stochastic_exponential (double a, double t): fage (a/t), tau (t) {};
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    return tau*log (1+ mcrx::rnd ()*(exp (fage) - 1));
  }
};


/** This functor takes a functor and returns NaN if that functor
    returns a value which is outside of the defined range. If the
    value is within the range it is just passed through. */
class mcrx::functors::range_clip {
private:
  T_particle_functor f_;
  const double min_;
  const double max_;
public:
  range_clip(const T_particle_functor& f, double minv, double maxv) : 
    f_(f), min_(minv), max_(maxv) {assert(minv<=maxv);};

  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    const double v=f_(num, s, pnum, ps);
    return ((v>=min_) && (v<=max_))? v : blitz::quiet_NaN(double());};
};


/** This functor returns an exponential declining value based on the
    distance between the parent particle and the origin.  */ 
class mcrx::functors::parent_radial_exponential {
private:
  const double c0_; ///< Central value
  const double h_; ///< Radial gradient
public:
  parent_radial_exponential (double c0, double h): c0_(c0), h_(h) {};

  /// Return c0*exp(-h*r), where r is the parent particle r.
  double operator()(int num, const particle_set_generic& s,
		    int pnum, particle_set_generic& ps) const {
    return c0_*exp(h_*sqrt(dot(ps.pos(pnum),ps.pos(pnum))));};
};


/** This functor compounds two functors and returns a pair of their
    two values.  */ 
class mcrx::functors::compound {
private:
  T_particle_functor f1;
  T_particle_functor f2;
public:
  compound(T_particle_functor a, T_particle_functor b) : f1(a), f2(b) {};

  std::pair<double,double> operator()(int num, const particle_set_generic& s,
				      int pnum, particle_set_generic& ps) const {
    return std::make_pair(f1(num, s, pnum, ps),
			  f2(num, s, pnum, ps));
  };
};

#endif
